package com.vivimice.lotroller.utils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import com.vivimice.lotroller.annotation.ListenAt;
import com.vivimice.lotroller.notification.NotificationAgent;
import com.vivimice.lotroller.notification.NotificationListener;
import com.vivimice.lotroller.notification.NotificationType;

public class NotificationUtils {

	/**
	 * auto associate listeners to declared notification types
	 * 
	 * @param obj
	 */
	public static void associateListeners(NotificationAgent agent, final Object obj) {
		Class<?> clazz = obj.getClass();
		for (final Method method : clazz.getDeclaredMethods()) {
			ListenAt listenerAnnotation = method.getAnnotation(ListenAt.class);
			if (listenerAnnotation == null) {
				continue;
			}
			String[] targets = listenerAnnotation.value();
			
			for (String target : targets) {
				final Field field;
				try {
					field = clazz.getDeclaredField(target);
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
				
				if (!NotificationType.class.isAssignableFrom(field.getType())) {
					throw new RuntimeException(field.getName() + " is not declared as NotificationType");
				}
				
				NotificationType type;
				try {
					type = (NotificationType) field.get(obj);
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
				
				if (type == null) {
					throw new NullPointerException(field.getName() + " is null");
				}
				
				agent.addListener(type, new NotificationListener() {
					@Override
					public void onNotify(NotificationType type, Object data) {
						Class<?>[] parameterTypes = method.getParameterTypes();
						try {
							if (parameterTypes.length == 0) {
								method.invoke(obj);
							} else if (parameterTypes.length == 1) {
								if (parameterTypes[0].isAssignableFrom(type.getClass())) {
									method.invoke(obj, type);
								} else {
									method.invoke(obj, data);
								}
							} else {
								method.invoke(obj, type, data);
							}
						} catch (Exception e) {
							throw new RuntimeException("Failed to invocate listener for field: " + field.getName(), e);
						}
					}
				});
			}
		}
	}
	
}
